package edu.wpi.cs509.ui;

import java.awt.Dimension;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import edu.wpi.cs509.clientlogic.ClientDownstreamLogic;
import edu.wpi.cs509.clientlogic.ClientLogic;
import edu.wpi.cs509.clientlogic.UserSession;
import edu.wpi.cs509.common.exceptions.ServerUnreachable;
import edu.wpi.cs509.ui.intfc.ClientLogicRequest;
import edu.wpi.cs509.ui.intfc.GameInterface;
import edu.wpi.cs509.ui.intfc.ClientLogicNotify;
import edu.wpi.cs509.ui.intfc.PlayerStatsInterface;
import edu.wpi.cs509.ui.simulators.ClientLogicSimulator;

/**
 * The StrategoClient class implements the application main for
 * the client.  
 * 
 * @author afoltan
 *
 */
public class A__MainStrategoClient {
	
	/**
	 * The main client window
	 */
	private StrategoClientFrame frame;
	
	/**
	 * The Personal Stats Panel
	 */
	private PersonalStatsPanel statsPanel;
	
	/**
	 * Leader Board Panel
	 */
	private LeaderboardPanel leaderPanel;

	/**
	 * The instance of the client logic interface which the UI will make calls
	 * to in order to initiate user actions. 
	 */
	private ClientLogicRequest clientLogic;

	/**
	 * The instance of the eventHandler for asynchronous client events
	 */
	private ClientLogicNotify notify;
	
	/**
	 * The active user session
	 */
	private UserSession session;
	
	/**
	 * Command line arguments
	 */
	private static boolean useClientSimulator = false;
	
	/**
	 * State of the client; used to determine what to display in the main frame
	 */
	private ClientState currentState = ClientState.LOGGED_OUT;

	/**
	 * The one and only active game 
	 */
	private GameWindow activeGame;
	
	/**
	 * The list of currently observed games; the key is the game ID
	 */
	private HashMap<Long,GameWindow> observedGames = new HashMap<Long,GameWindow>();
	
	
	/**
	 * Constructor for the client application
	 */
	public A__MainStrategoClient() {
		
		// Instantiate the client logic and provide the event handler
		notify = new ClientLogicNotifyHandler( this );

		if (useClientSimulator) {
			// Instantiate the client logic simulator (for development)
			clientLogic = new ClientLogicSimulator( notify );
		} else {
			
			// 'Instantiate' the client logic
			clientLogic = ClientDownstreamLogic.getInstance();
			
			// Set the UI notify handler in the client logic
			ClientLogic upLogic = ClientLogic.getInstance();
			upLogic.setSessionEventHandle(notify);
		}

		// Create the frame
		frame = new StrategoClientFrame( this );
	}
	
	private static void parseArgs( String[] args ) {
		for (String s : args) {
			if (s.equals("-sim")) {
				useClientSimulator = true;
			}
		}
	}
	
	/**
	 * The Stratego Client application main
	 * 
	 * @param args - command line parameters (none supported)
	 */
	public static void main(String[] args) {
		
		parseArgs(args);
		
		// Instantiate the client application
		A__MainStrategoClient clientApp = new A__MainStrategoClient();
		
		// Display the main frame
		clientApp.displayMainFrame();
	}

	/**
	 * Enables the client application main frame for display
	 */
	public void displayMainFrame() {
		Dimension dim;
		
		switch( currentState ) {
		case LOGGED_OUT:
			dim = new Dimension(300,200);
			
			frame.setPreferredSize( dim );
			frame.setSize( dim );
			
			frame.setVisible(true);
			frame.setTitle("CS509 Stratego");
			break;
		case LOGGED_IN:
			dim = new Dimension(400,500);
			
			frame.setPreferredSize( dim );
			frame.setSize( dim );
			
			frame.setVisible(true);
			frame.setTitle("CS509 Stratego");
			break;
		}
		
	}
	
	/**
	 * Quit the client application
	 */
	public void quit() {
		if (this.currentState == ClientState.LOGGED_IN) {
			clientLogic.logout( session );
		}
		System.exit(0);
	}
	
	/**
	 * Requests the client logic to retrieve the list of games from
	 * the server
	 */
	public void getGames() {
		try {
			System.out.println("UI: ----> requestGames from clientLogic ---->");
			clientLogic.requestGames();
		} catch (ServerUnreachable e) {
			doServerUnreachableCleanup();
		}
	}
	
	/**
	 * Sets the user session for this client.  The user session is returned
	 * asynchronously by the login event.
	 * 
	 * @param session - the user session returned by the client logic
	 */
	public void setUserSession( UserSession session ) {
		this.session = session;
	}
	
	/**
	 * Gets the user session. 
	 */
	public UserSession getUserSession() {
		return session;
	}

	/**
	 * Sets the state of this client.  Certain state transitions will cause
	 * the client frame to update.  For example, transition from logged out
	 * to logged in will change the main panel to the games list.
	 * 
	 * @param newState - the new client state.
	 */
	public void setState(ClientState newState) {

		if ((currentState == ClientState.LOGGED_OUT) &&
				(newState == ClientState.LOGGED_IN)) {
			// on transition to logged in, we fetch the games list
			getGames();
		}

		this.currentState = newState;
	}
	
	/**
	 * Retrieves the client frame for this application
	 * @return
	 */
	public StrategoClientFrame getClientFrame() {
		return frame;
	}

	/**
	 * Retrieves the client logic object
	 * @return
	 */
	public ClientLogicRequest getClientLogic() {
		return clientLogic;
	}

	/**
	 * Opens the leaderboard window.  This occurs in response to the 
	 * asynchronous client notification.
	 * 
	 * @param statsList - the list of player stats to display
	 * @param rank - the starting rank
	 */
	public void displayLeaderBoard(List<PlayerStatsInterface> statsList,
			int rank) {
		// FIXME - should instantiate a Leaderboard object
		leaderPanel.UpdateLeaderboard(statsList);
	}

	/**
	 * Updates the player stats window.  This occurs in response to the 
	 * asynchronous client notification.
	 * @param myStats - the player stats
	 */
	public void displayPlayerStats(PlayerStatsInterface myStats) {
		statsPanel.UpdateStats(myStats);
	}

	/**
	 * Indicates the state of the client application is logged in.
	 * @return
	 */
	public boolean isLoggedIn() {
		return currentState == ClientState.LOGGED_IN;
	}
	
	/**
	 * Requests the client logic to create a new game.  In the initial
	 * release, only one active game is supported.  If called while
	 * a game is already in progress, a dialog error message will be
	 * presented to the user.
	 * 
	 */
	public void startNewGame() {

		if (activeGame == null) {
			
			// create the game board window for playing
			activeGame = new GameWindow( this );
						
			// create the event handler for this window
			GameEventHandler handler = new GameEventHandler( activeGame );
			
			try {
				clientLogic.startNewGame(session, handler);
			} catch (ServerUnreachable e) {
				doServerUnreachableCleanup();
			}
		} else {
			Dialog.displayOneGameDialog();
		}
	}
	
	/**
	 * Requests the client logic to join a game.  In the initial
	 * release, only one active game is supported.  If called while
	 * a game is already in progress, a dialog error message will be
	 * presented to the user.
	 * 
	 */
	public void joinGame( long gameID ) {

		if (activeGame == null) {
			
			// create the game board window for playing
			activeGame = new GameWindow( this );
						
			// create the event handler for this window
			GameEventHandler handler = new GameEventHandler( activeGame );
			
			try {
				clientLogic.joinGame(session, gameID, handler);
			} catch (ServerUnreachable e) {
				doServerUnreachableCleanup();
			}
		} else {
			Dialog.displayOneGameDialog();
		}
	}
	
	/**
	 * Requests the client logic to begin observing a game.  Multiple 
	 * games can be observed at the same time.
	 * @param game - the game to observe
	 */
	public void addObserveGame( long gameID ) {
				
		// Check if this game is already observed
		GameWindow gameWindow = observedGames.get(gameID);
		if (gameWindow != null) {
			//gameWindow.bringToFront();
			System.out.println("This game is already observed");
			return;
		}
		
		try {
			
			// create the new game board window for observing
			GameWindow observationWindow = new GameWindow( this );
			
			// create the event handler for this window
			GameEventHandler handler = new GameEventHandler( observationWindow );
			
			// add the window to our observed list
			observedGames.put(gameID, observationWindow );		
			
			// request to start observing
			clientLogic.spectateStart(session, gameID, handler);

		} catch (ServerUnreachable e) {
			
			doServerUnreachableCleanup();
		}
		
	}

	/**
	 * Sets the game object into the current active game.  This occurs in response to the 
	 * asynchronous client notification. 
	 * @param newGame - the game to become active
	 */
	public void setActiveGame(GameInterface game) {
		
		if (activeGame != null) {
			activeGame.setGameInterface(game);
		} 
							
	}

	/**
	 * Sets the game object into an observed game.  This occurs in response to the 
	 * asynchronous client notification. 
	 * @param game - the game being observed
	 */
	public void setObservedGame(GameInterface game) {
		
		GameWindow observedGame = observedGames.get(game.getGameID());
		
		if (observedGame != null) {
			observedGame.setGameInterface(game);
		} 
							
	}
	
	/**
	 * Sets the statsPanel panel to point to the panel in the popped out
	 * Personal Stats Frame,
	 * @param panel - panel to be assigned to statsPanel
	 */
	public void setStatsPanel(PersonalStatsPanel panel) {
		this.statsPanel = panel;
	}
	
	/**
	 * Sets the leaderPanel panel to point to the panel in the popped out
	 * Leader Board Frame,
	 * @param panel - panel to be assigned to leaderPanel
	 */
	public void setLeaderPanel(LeaderboardPanel panel) {
		this.leaderPanel = panel;
	}
	
	/**
	 * In the event the server becomes unreachable during any request
	 * to the client logic, the cleanup is always the same.
	 * - tell the user
	 * - close all game windows
	 * - return to the login screen
	 */
	private void doServerUnreachableCleanup() {
		
		// inform user
		Dialog.displayServerUnreachableDialog();
		
		// close all windows
		if (activeGame != null) {
			activeGame.quit();
			activeGame = null;
		}
		
		GameWindow observedGame;
		Iterator<GameWindow> itr = observedGames.values().iterator();
		while (itr.hasNext()) {
			observedGame = itr.next();
			observedGame.quit();
			itr.remove();
		}
		
		// back to login
		this.getClientFrame().displayLoginPanel();
	}

	/**
	 * This method is invoked when the user closes a game window.
	 * 
	 * @param l - the game ID associated with the closing window
	 */
	public void gameWindowClosing(long l) {
		
		// the game window object will be removed from this client 
		// but first we need to determine whether it was the active
		// game, or one of the observed games.
		
		// idempotent remove
		observedGames.remove(l);		
		
		// check for the active game
		if (activeGame != null) {
			if (activeGame.getGameID() == l) {
				activeGame = null;
			}
		}
		
	}
	
}
